/*
 * Copyright (c) 2009. Silenus Consultoria, S.L.
 */
package com.tonbeller.jpivot.print;

import com.tonbeller.jpivot.chart.ChartComponent;

import com.tonbeller.jpivot.table.TableComponent;

import com.tonbeller.wcf.component.RendererParameters;

import com.tonbeller.wcf.controller.RequestContext;
import com.tonbeller.wcf.controller.RequestContextFactoryFinder;

import com.tonbeller.wcf.utils.XmlUtils;

import org.apache.avalon.framework.logger.NullLogger;

import org.apache.fop.apps.Driver;
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.Options;

import org.apache.fop.configuration.Configuration;

import org.apache.fop.messaging.MessageHandler;

import org.apache.log4j.Logger;

import org.w3c.dom.Document;

import org.xml.sax.InputSource;

import java.io.*;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;


/**
 * Expected HTTP GET Parameters: - cube - the jpivot cube id, used to lookup table, chart, and print references -
 * type - the output type, 0 for xls, 1 for pdf - filenamePre - (optional) - defaults to xls_export, specifies the
 * filename the browser will use to name the output.
 */
public class PrintServlet extends HttpServlet {
	private static Logger logger = Logger.getLogger(PrintServlet.class);
	private static final String DEFAULT_ORIENTATION = "landscape";
	private static final String DEFAULT_PAPER_TYPE = "A3";
	private static final int XML = 0;
	private static final int PDF = 1;
	String basePath;
	String filename;

	/**
	 * Initializes the servlet.
	 *
	 * @param config the configuration.
	 *
	 * @throws ServletException if something goes wrong.
	 */
	public void init(ServletConfig config) throws ServletException {
		super.init(config);

		try {
			// set base FOP FONT directory.  The font config  stuff will be looked for here
			Configuration.put("fontBaseDir", config.getServletContext().getRealPath("/WEB-INF/jpivot/print/"));

			// get the physical path for the config file
			String fopConfigPath = config.getServletContext().getRealPath("/WEB-INF/jpivot/print/userconfig.xml");

			// load the user proerties, contining the CustomFont font.
			new Options(new File(fopConfigPath));
		} catch(FOPException e) {
			logger.error("FOP user config file not loaded", e);
		} catch(Exception e) {
			logger.error("FOP user config file not loaded", e);
		}
	}

	/**
	 * Destroys the servlet.
	 */
	public void destroy() {
	}

	/**
	 * Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
	 *
	 * @param context the request context.
	 *
	 * @throws ServletException if something goes wrong.
	 * @throws IOException if something goes wrong.
	 */
	protected void processRequest(RequestContext context) throws ServletException, IOException {
		HttpServletRequest request = context.getRequest();
		HttpServletResponse response = context.getResponse();

		if((request.getParameter("cube") != null) && (request.getParameter("type") != null)) {
			try {
				String xslUri = null;
				String filename = null;
				int type = Integer.parseInt(request.getParameter("type"));
				String filenamePre = "xls_export";

				if(request.getParameter("filenamePre") != null) {
					filenamePre = request.getParameter("filenamePre");
				}

				switch(type) {
					case XML:
						xslUri = "/WEB-INF/jpivot/table/xls_mdxtable.xsl";
						RendererParameters.setParameter(context.getRequest(), "mode", "excel", "request");
						response.setContentType("application/vnd.ms-excel");
						filename = filenamePre + ".xls";

						break;

					case PDF:
						xslUri = "/WEB-INF/jpivot/table/fo_mdxtable.xsl";
						RendererParameters.setParameter(context.getRequest(), "mode", "print", "request");
						response.setContentType("application/pdf");
						filename = filenamePre + ".pdf";

						break;
				}

				if(xslUri != null) {
					boolean xslCache = true;

					// get references to needed elements
					String tableRef = "table" + request.getParameter("cube");
					String chartRef = "chart" + request.getParameter("cube");
					String printRef = "print" + request.getParameter("cube");

					Map<String, Object> parameters = new HashMap<String, Object>();

					OutputStream outStream = response.getOutputStream();
					PrintWriter out = new PrintWriter(outStream);
					HttpSession session = request.getSession();

					// set up filename for download.
					response.setHeader("Content-Disposition", "attachment; filename=" + filename);

					// get TableComponent
					TableComponent table = (TableComponent)context.getModelReference(tableRef);

					// only proceed if table component exists
					if(table != null) {
						// add parameters from printConfig
						PrintComponent printConfig = (PrintComponent)context.getModelReference(printRef);

						if(printConfig != null) {
							if(printConfig.isSetTableWidth()) {
								parameters.put(printConfig.PRINT_TABLE_WIDTH, printConfig.getTableWidth());
							}

							if(printConfig.getReportTitle().trim().length() != 0) {
								parameters.put(printConfig.PRINT_TITLE, printConfig.getReportTitle().trim());
							}

							// parameters.put(printConfig.PRINT_PAGE_ORIENTATION, printConfig.getPageOrientation());
							parameters.put(printConfig.PRINT_PAGE_ORIENTATION, DEFAULT_ORIENTATION);
							// parameters.put(printConfig.PRINT_PAPER_TYPE, printConfig.getPaperType());
							parameters.put(printConfig.PRINT_PAPER_TYPE, DEFAULT_PAPER_TYPE);

							if(printConfig.getPaperType().equals("custom")) {
								parameters.put(printConfig.PRINT_PAGE_WIDTH, printConfig.getPageWidth());
								parameters.put(printConfig.PRINT_PAGE_HEIGHT, printConfig.getPageHeight());
							}

							parameters.put(printConfig.PRINT_CHART_PAGEBREAK, printConfig.isChartPageBreak());
						}

						String host = request.getServerName();
						int port = request.getServerPort();
						String location = request.getContextPath();
						String scheme = request.getScheme();
						final String contextURL = scheme + "://" + host + ":" + port + location;


						// add parameters and image from chart if visible
						ChartComponent chart = (ChartComponent)request.getSession().getAttribute(chartRef);

						if((chart != null) && chart.isVisible()) {
							String chartServlet = contextURL + "/GetChart";

							parameters.put("chartimage", chartServlet + "?filename=" + chart.getFilename());
							parameters.put("chartheight", chart.getChartHeight());
							parameters.put("chartwidth", chart.getChartWidth());
						}

						//parameters.put("message",table.getReportTitle());
						// add "context" and "renderId" to parameter map

						//parameters.put("renderId", renderId);
						parameters.put("context", context.getRequest().getContextPath());

						// Some FOP-PDF versions require a complete URL, not a path
						parameters.put("contextUrl", contextURL);

						table.setDirty(true);

						Document document = table.render(context);

						table.setDirty(true);

						if(logger.isDebugEnabled()) {
							StringWriter dw = new StringWriter();

							XmlUtils.print(document, dw);
							logger.debug("Document: " + dw.toString());
						}

						// query = context.getOutputParameter( "mdx" ).getStringValue()
						// query01 --> context.getModelReference(tableRef)
						DOMSource source = new DOMSource(document);

						// set up xml transformation
						Transformer transformer = XmlUtils.getTransformer(session, xslUri, xslCache);

						for(Map.Entry<String, Object> entry : parameters.entrySet()) {
							transformer.setParameter(entry.getKey(), entry.getValue());
						}

						StringWriter sw = new StringWriter();
						StreamResult result = new StreamResult(sw);

						//do transform
						transformer.transform(source, result);
						sw.flush();

						// if thisis XML, then we are done, so output xml file.
						if(type == XML) {
							// Turn off logging
							MessageHandler.setScreenLogger(new NullLogger());

							response.setContentLength(sw.toString().length());
							out.write(sw.toString());
							RendererParameters.removeParameter(context.getRequest(), "mode", "excel", "request");

							// process FO to PDF
						} else {
							try {
								// Turn off logging
								MessageHandler.setScreenLogger(new NullLogger());

								ByteArrayInputStream bain = new ByteArrayInputStream(sw.toString().getBytes("UTF-8"));
								ByteArrayOutputStream baout = new ByteArrayOutputStream(16384);

								convertFO2PDF(bain, baout);

								final byte[] content = baout.toByteArray();

								response.setContentLength(content.length);
								outStream.write(content);
								RendererParameters.removeParameter(context.getRequest(), "mode", "print", "request");
							} catch(Exception e) {
								logger.error("Error creating pdf", e);
							}
						}

						//close output streams
						out.flush();
						out.close();
						outStream.flush();
					}
				}
			} catch(Exception e) {
				logger.error("Error printing pivot cube", e);
			}
		}
	}

	/**
	 * Converts FO xml into PDF using the FOP processor.
	 *
	 * @param bain input stream.
	 * @param baout output stream.
	 *
	 * @throws IOException if an I/O error occurs.
	 * @throws FOPException if something goes wrong.
	 */
	@SuppressWarnings({"EmptyFinallyBlock"})
	public void convertFO2PDF(ByteArrayInputStream bain, ByteArrayOutputStream baout) throws IOException, FOPException {
		if(logger.isDebugEnabled()) {
			logger.debug("Construct driver");
		}

		Driver driver = new Driver();

		if(logger.isDebugEnabled()) {
			logger.debug("Setup Renderer (output format)");
		}

		driver.setRenderer(Driver.RENDER_PDF);

		try {
			driver.setOutputStream(baout);

			if(logger.isDebugEnabled()) {
				logger.debug("Setup input");
			}

			try {
				driver.setInputSource(new InputSource(bain));

				if(logger.isDebugEnabled()) {
					logger.debug("Process FO");
				}

				driver.run();

				if(logger.isDebugEnabled()) {
					logger.debug("PDF file generation completed");
				}
			} finally {
				// empty
			}
		} finally {
			// empty
		}
	}

	/**
	 * Handles the HTTP <code>GET</code> method.
	 *
	 * @param request servlet request
	 * @param response servlet response
	 *
	 * @throws ServletException if something goes wrong.
	 * @throws IOException if something goes wrong.
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doPost(request, response);
	}

	/**
	 * Handles the HTTP <code>POST</code> method.
	 *
	 * @param request servlet request
	 * @param response servlet response
	 *
	 * @throws ServletException if something goes wrong.
	 * @throws IOException if something goes wrong.
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		RequestContext context = RequestContextFactoryFinder.createContext(request, response, true);

		try {
			processRequest(context);
		} finally {
			context.invalidate();
		}
	}

	/**
	 * Returns a short description of the servlet.
	 *
	 * @return the servlet information.
	 */
	public String getServletInfo() {
		return "Export OLAP table";
	}
}
